﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dycox.Workflows.Data;
using Dycox.Linq.Data;

namespace Dycox.Workflows
{
    internal abstract class DataProvider : IDisposable
    {
        public DataProvider(object dataSource)
        {
            if (dataSource == null)
                throw new ArgumentNullException("dataSource");

            _dataSource = dataSource;
        }

        public abstract void Insert(object obj);
        public abstract IWorkflowComponent GetComponent(ComponentIdentifier id, bool throwOnError);
        public abstract void DeleteComponent(ComponentIdentifier id);
        public abstract void DeepDeleteProcess(WFProcess process);
        public abstract IQueryable<WFThread> WFThreads { get; }
        public abstract IQueryable<WFProcess> WFProcesses { get; }
        public abstract IQueryable<WFStep> WFSteps { get; }
        protected abstract void SubmitChangesCore();
        public abstract WFFrame PopFrame(Guid processId);
        public abstract void PushFrame(WFFrame frame);
        public abstract void ClearFrames(Guid processId);
        public abstract void InitializeStepQuery(DataArguments args);
        public abstract void InitializeThreadQuery(DataArguments args);
        public abstract void InitializeProcessQuery(DataArguments args);
        internal abstract void FillUndoState(WorkflowUndoState state, WorkflowExecutionContext context);
        internal abstract EntityKey GetEntityKey(object entity);
        internal abstract object GetEntity(EntityKey key, Type type);
        internal abstract void DeleteEntity(EntityKey key, Type type);
        internal abstract bool HasChanges { get; }
        internal abstract void BeginTransaction();
        internal abstract void CommitTransaction();
        internal abstract void RollbackTransaction();

        internal void ValidateNoChanges()
        {
            if (this.HasChanges)
                throw new WorkflowException(SR.GetString("cannot_have_changes"));

        }

        public event EventHandler SubmittingChanges;

        public void SubmitChanges()
        {
            if (SubmittingChanges != null)
                SubmittingChanges(this, EventArgs.Empty);

            SubmitChangesCore();
        }

        public WFProcess GetProcess(ComponentIdentifier id)
        {
            WFProcess proc = GetComponent(id, true) as WFProcess;
            if (proc == null)
                throw new ArgumentException(SR.GetString("invalid_component_id"), "id");

            return proc;
        }

        public virtual void DeleteProcess(Guid processId)
        {
            DeleteComponent(new ComponentIdentifier(processId));
            ClearFrames(processId);
        }

        public abstract WFProcess[] GetChildProcesses(Guid parentId);

        // TODO: TransactionScope supporting
        internal abstract bool TransactionExists { get; }

        private bool _shouldDisposeDataSource = true;

        public bool ShouldDisposeDataSource
        {
            get { return _shouldDisposeDataSource; }
            set { _shouldDisposeDataSource = value; }
        }

        private object _dataSource;

        public object DataSource
        {
            get { return _dataSource; }
        }

        public virtual void Dispose()
        {
            if (_shouldDisposeDataSource)
            {
                IDisposable obj = _dataSource as IDisposable;
                if (obj != null) obj.Dispose();
            }
        }

        /// <summary>
        /// Returns the default data provider.
        /// </summary>
        /// <returns></returns>
        internal static DataProvider CreateProvider()
        {
            return CreateProvider(new WorkflowDataContext(WorkflowManager.ConnectionString));
        }

        internal static DataProvider CreateProvider(object dataSource)
        {
            if (dataSource == null)
                throw new ArgumentNullException("dataSource");

            if (dataSource is System.Data.Linq.DataContext)
                return new LinqToSqlDataProvider((System.Data.Linq.DataContext)dataSource);
            else
                throw new NotSupportedException("Data source type " + dataSource.GetType().FullName + " is not supported yet.");
        }

    }
}
